/**
 * 2009 3 Jul 2009
/*

/**
 * 
 */
package org.idimitrov.dirtools.dirlister;

import java.io.File;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Contains methods for listing the contents of a directory in the file system.
 * 
 * @author Ivaylo Dimitrov
 */
public class DirLister {
    /**
     * The OS file separator.
     */
    private static final String FILE_SEPARATOR = System
	    .getProperty("file.separator");
    /**
     * The default offset character (must be 1 character long).
     */
    private static final String OFFSET = " ";

    /**
     * A character used for vertical linking between the items (must be 1
     * character long).
     */
    private static final String VERTICAL_LINKER = ".";

    /**
     * A prefix before each filename to be printed.
     */
    private static final String NAME_PREFIX = "|";

    /**
     * The offset from the position of the root path printed.
     */
    private int currentOffset = 0;

    /**
     * Keeps track of all offset changes.
     */
    private List<Integer> offsets = new ArrayList<Integer>();

    /**
     * Lists the contents of the given path if it exists as a directory or a
     * file and prints the list to the output given. NOTE: Please use fixed
     * width fonts for proper displaying.
     * 
     * @param path
     *            the path to the item
     * @param out
     *            the output where the list will be written
     * @param formatted
     *            will the output be formatted - true for formatted, false
     *            otherwise
     */
    public void list(String path, PrintStream out, boolean formatted) {
	File file = new File(path);
	if (formatted) {
	    if (file.isDirectory()) {
		String[] splitPath = file.getPath()
			.split("\\" + FILE_SEPARATOR);
		for (int i = 0; i < splitPath.length - 1; i++) {
		    print(out, new StringBuffer(splitPath[i] + FILE_SEPARATOR),
			    currentOffset);
		    currentOffset = getNewOffset(currentOffset, splitPath[i]
			    .length());
		}

		// doing that because if we pass the top dir in the filesystem
		// hierarchy, it won't be printed
		if (splitPath.length == 1) {
		    print(out, new StringBuffer(splitPath[0] + FILE_SEPARATOR),
			    currentOffset);
		    currentOffset = getNewOffset(currentOffset, splitPath[0]
			    .length());
		}
		listContentsFormatted(file, out);
	    } else if (file.isFile()) {
		print(out, new StringBuffer(file.getAbsolutePath()), 0);
	    } else {
		throw new RuntimeException("The file/directory does not exist.");
	    }
	} else {
	    if (file.isFile() || file.isDirectory()) {
		listContents(file, out);
	    } else {
		throw new RuntimeException("The file/directory does not exist.");
	    }
	}
    }

    /**
     * Lists recursively the contents of <code>root</code> and prints it to
     * <code>out</code>.
     * 
     * @param root
     *            the <code>{@link File}</code> which contents to be printed
     * @param out
     *            the output where the contents of <code>root</code> to be
     *            printed
     */
    private void listContents(File root, PrintStream out) {
	File[] array = root.listFiles();
	if (array == null) {
	    out.println(root.getAbsolutePath());
	} else {
	    for (int i = 0; i < array.length; i++) {
		listContents(array[i], out);
	    }
	}
    }

    /**
     * Lists recursively the contents of <code>root</code> and prints it to
     * <code>out</code> using formatting.
     * 
     * @param root
     *            the <code>{@link File}</code> which contents to be printed
     * @param out
     *            the output where the contents of <code>root</code> to be
     *            printed
     */
    private void listContentsFormatted(File root, PrintStream out) {
	// doing that check because in Windows if you pass a drive letter only,
	// getName() returns "" and we don't want to print it again
	if (!"".equals(root.getName())) {
	    StringBuffer nameToPrint = new StringBuffer(NAME_PREFIX
		    + root.getName());
	    if (root.isFile()) {
		print(out, nameToPrint, currentOffset);
	    } else if (root.isDirectory()) {
		print(out, nameToPrint.append(FILE_SEPARATOR), currentOffset);
	    }
	    File[] array = root.listFiles();
	    if (array != null) {
		int old = currentOffset;
		offsets.add(old);
		// here, nameToPrint is a dir name and normally we don't count
		// the appended FILE_SEPARATOR in calculating the new offset
		// position; that's why we use nameToPrint.length() - 1
		currentOffset = getNewOffset(currentOffset, nameToPrint
			.length() - 1);
		for (int i = 0; i < array.length; i++) {
		    listContentsFormatted(array[i], out);
		}
		currentOffset = old;
		offsets.remove(offsets.size() - 1);
	    }
	} else {
	    File[] array = root.listFiles();
	    if (array != null) {
		for (int i = 0; i < array.length; i++) {
		    listContentsFormatted(array[i], out);
		}
	    }
	}
    }

    /**
     * Prints the given <code>item</code> to the <code>out</code> using the
     * given <code>offset</code>.
     * 
     * @param out
     *            the output where the <code>item</code> to be printed
     * @param item
     *            to be printed to <code>out</code>
     * @param offset
     *            number of steps from the root item
     */
    private void print(PrintStream out, StringBuffer item, int offset) {
	StringBuffer temp = new StringBuffer(item.toString());
	for (int i = 0; i < offset; i++) {
	    temp.insert(0, OFFSET);
	}
	if (offsets.size() > 0) {
	    for (Iterator<Integer> iterator = offsets.iterator(); iterator
		    .hasNext();) {
		Integer o = iterator.next();
		temp.replace(o, o + 1, VERTICAL_LINKER);
	    }
	}
	out.println(temp.toString());
    }

    /**
     * Calculates a new offset depending on the length of <code>rootName</code>
     * and the current offset.
     * 
     * @param offset
     *            the current offset
     * @param nameLength
     *            the length of the name of the item to be printed
     * @return the calculated new offset
     */
    private int getNewOffset(int offset, int nameLength) {
	int temp = offset;
	for (int i = 0; i < nameLength; i++) {
	    temp++;
	}
	return temp;
    }

    /**
     * Main method used for testing purposes.
     * 
     * @param args
     *            default args
     */
    public static void main(String[] args) {
	DirLister lister = new DirLister();
	lister.list("D:/Development", System.out, true);
    }
}
